<?php
/**
 * 处理开放平台授权事件
 */
namespace App\Http\Controllers\OpenWechat;

use App\Http\Requests\OpenWechat\Authorizer\JSSDKRequest;
use App\Http\Requests\OpenWechat\Authorizer\QrCodeRequest;
use App\Http\Requests\OpenWechat\Authorizer\RouteAppidRequest;
use App\Models\OpenWechat\Authorizer;
use App\Models\OpenWechat\ComponentToken;
use App\Services\OpenWechat\AppFactory;
use App\Services\OpenWechat\AuthorizerRepository;
use App\Services\OpenWechat\Mock;
use App\Services\Utils\ApiChecker;
use App\Services\Utils\ApiResponser;
use EasyWeChat\Kernel\Support\Collection;
use EasyWeChat\OpenPlatform\Server\Guard;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Symfony\Component\HttpFoundation\RedirectResponse;

class AuthorizerController extends Controller
{
    use ApiResponser, ApiChecker;

    const AUTHOR_PRE_AUTH_CALLBACK_ROUTE = 'authorizer-pre-auth-callback';

    /**
     * @var \EasyWeChat\OpenPlatform\Application
     */
    protected $open_wx;
    /**
     * @var AuthorizerRepository
     */
    private $authorizerRepository;

    public function __construct(AuthorizerRepository $authorizerRepository)
    {
        $this->open_wx = AppFactory::openWechat();

        $this->authorizerRepository = $authorizerRepository;
    }


    /**
     * 开放平台接收微信公众号授权事件消息
     *
     * @param Request $request
     * @return \Symfony\Component\HttpFoundation\Response
     * @throws \EasyWeChat\Kernel\Exceptions\BadRequestException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     */
    public function auth(Request $request)
    {
        $server = $this->open_wx->server;

        $this->handleAuthByType($server);

        $response = $server->serve();

        return $response;
    }

    /**
     * @param Guard $server
     * @return void
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     */
    private function handleAuthByType(Guard $server)
    {
        // 授权成功
        $server->push(function(array $msg) {
            $event = new Collection($msg);

            $this->log($event);
            return $this->handleAuthorized($event);
        }, Guard::EVENT_AUTHORIZED);

        // 更新授权
        $server->push(function(array $msg) {
            $event = new Collection($msg);

            $this->log($event);
            return $this->handleAuthorizeUpdate($event);
        }, Guard::EVENT_UPDATE_AUTHORIZED);

        // 授权取消
        $server->push(function(array $msg) {
            $event = new Collection($msg);

            $this->log($event);
            return $this->handleAuthorizeCancel($event);
        }, Guard::EVENT_UNAUTHORIZED);

        // 保存 Verify Ticket
        $server->push(function(array $msg) {
            $event = new Collection($msg);

            $this->log($event);
            return $this->handleVerifyTicket($event);
        }, Guard::EVENT_COMPONENT_VERIFY_TICKET);
    }


    /**
     * 获取预授权 URL， 重定向公众号到预授权页面
     *
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|string
     */
    public function preAuthUrl()
    {
        $open_wx = AppFactory::openWechat();

        /** @var RedirectResponse $response */
        $url = $open_wx
            ->getPreAuthorizationUrl(route(self::AUTHOR_PRE_AUTH_CALLBACK_ROUTE));

        // 不能直接重定向访问这个地址，因为微信判断必须从相同域名的页面跳转过去才行
        // TODO: 可否用网页301重定向或网页JS重定向？
        return "<a href='$url'>".trans('open_wechat.auth')."</a>";
    }

    /**
     * 授权完成的回调，处理预授权成功回调
     * 返回的 auth_code 就是 $event->AuthorizationCode
     *
     * @test
     *
     * @param Request $request
     *
     * @return string
     * @throws \Exception
     */
    public function preAuthCallback(Request $request)
    {
        $auth_code = $request->auth_code;
            $this->checkParamError($auth_code, 'auth code not found');

        $author = Authorizer::findByAuthCode($auth_code);
        if (! $author) {
            return trans('open_wechat.authorizer_not_found');
        }

        if (! $author->isTypeService()) {
            return trans('app.open_wechat.authorizer_is_not_type_service');
        }

        // query auth code in db get authorizer name
        return trans('open_wechat.pre_auth', [
            'name' => $author->nick_name,
            'code' => $auth_code
        ]);
    }

    /**
     * @param Collection $event
     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    private function handleVerifyTicket(Collection $event)
    {
        /**
         * @see Mock::verifyTicketEvent()
         */
        ComponentToken::updatePlatformVerifyTicket(
            $event->AppId,
            $event->ComponentVerifyTicket
        );

        $this->checkUpdateOpenPlatformCompAcsToken($event->AppId);
    }

    /**
     * @param Collection $event
     */
    private function handleAuthorized(Collection $event)
    {
        /**
         * @see Mock::authorizeEvent()
         * @see Mock::openWxGetAuthorizerInfo()
         */
        $this->saveOrUpdateAuthorizerInfo($event);
    }

    /**
     * @param $event
     */
    private function handleAuthorizeUpdate(Collection $event)
    {
        /**
         * @see Mock::authorizeUpdateEvent()
         */
        $this->saveOrUpdateAuthorizerInfo($event);
    }

    /**
     * @param Collection $event
     */
    private function handleAuthorizeCancel(Collection $event)
    {
        /**
         * @see Mock::authorizeCancelEvent()
         */
        $canceled = Authorizer::cancelAuth($event->AuthorizerAppid);
    }

    /**
     * 获取授权公众号信息
     *
     * @param RouteAppidRequest $request
     * @return Collection
     */
    public function authorizerInfo(RouteAppidRequest $request)
    {
        /**
         * @see Mock::openWxGetAuthorizerInfo()
         */
        return $this->open_wx->getAuthorizer($request->route('appid'));
    }

    /**
     * 获取开放平台的 ACS TOKEN
     *
     * @return string
     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function componentAccessToken()
    {
        return $this->getOpenPlatformAcsToken();
    }

    /**
     * 返回  Component Acs Token 用于本地测试
     *
     * @return string
     *
     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    private function getOpenPlatformAcsToken()
    {
        /**
         * @example - !!! 如无法使用这个方法可能要看下 本机IP 是否在白名单上
         *
          [
            "component_access_token" => "7_VqFMM1Q8ttUQz5d3NT1fVS8WRJSllOOgOJAlLBpjlU0WWOWa-7fOHrfst9UE_RrXmZ--AOW4xZSm2k9Od6zKJxajnAnPjk7Dx4pNWMyal-4GsbpUwANJWo5hASlmYbwBm76OQbKM50fGHCesZWGgABADAK",
            "expires_in" => 7200,
          ]
         */
        $token = $this->open_wx->access_token->getToken();

        return $token['component_access_token'];
    }

    /**
     * 返回指定公众号的菜单
     *
     * @param RouteAppidRequest $request
     * @return Collection
     */
    public function authorizerMenu(RouteAppidRequest $request)
    {
        $app = (new AppFactory())->authorizerApp($request->appid);

        return $app->menu->list();
    }

    /**
     * 返回指定 authorizer 的 acs token
     *
     * @param RouteAppidRequest $request
     * @return array
     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function accessToken(RouteAppidRequest $request)
    {
        $app = (new AppFactory())->authorizerApp($request->appid);
    
        /**
         * @see Mock::appAccessTokenGet()
         */
        $token = $app->access_token->getToken();

        return $this->response(compact('token'));
    }

    /**
     * 授权成功与更新权限共用更新授权公众号信息方法
     *
     * @param Collection $event
     */
    private function saveOrUpdateAuthorizerInfo(Collection $event)
    {
        $auth_code = $event->AuthorizationCode;
        /**
         * @see Mock::openWxGetAuthorizationInfo()
         *
         * @var array $authorization_info
         *
         * 这里一定向微信平台请求的，参数也要传，不然很容易出错
         */
        $authorization_info = $this->open_wx
            ->handleAuthorize($auth_code)['authorization_info'];

        /** @var Authorizer $author */
        $author = $this->authorizerRepository
            ->firstOrCreateAuthorizationInfo($authorization_info['authorizer_appid']);
    
        /**
         * @see Mock::openWxGetAuthorizerInfo()
         *
         * @var Collection $authorize_info
         * 这个里面也有一个 authorization_info , 但不能与上面那个弄混
         * 这个是不请求的，东西都是之前缓存起来的
         */
        $authorize_info = $this->open_wx->getAuthorizer($event->AuthorizerAppid);
    
        /** @var array $author_info */
        $author_info = $authorize_info['authorizer_info'];

        $author->updateAuthorizerInfo($author_info, $authorization_info, $auth_code);
    }

    /**
     * 检查如果是 info culti 的开放平台 appid 就更新 accessToken
     *
     * @param $appid
     * @throws \EasyWeChat\Kernel\Exceptions\HttpException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     * @internal param Collection $event
     */
    private function checkUpdateOpenPlatformCompAcsToken($appid)
    {
        if (! $this->isOpenPlatformAppId($appid)) return;

        $open_platform_component = ComponentToken::findByAppId($appid);

        if (! $open_platform_component) return;

        $open_platform_component->updateAccessToken($this->getOpenPlatformAcsToken());
    }

    /**
     * @param $appid
     * @return bool
     */
    private function isOpenPlatformAppId($appid)
    {
        return $appid === config('pkx.open_wechat.app_id');
    }

    /**
     * 返回指定授权公众号appid 的二维码，可以生成临时/永久二维码
     *
     * @param QrCodeRequest|Request $request
     * @param $appid
     * @return array
     */
    public function AuthorizerQrCode(QrCodeRequest $request, $appid)
    {
        $qrcode = (new AppFactory())->authorizerApp($appid)->qrcode;

        $scene_value = $request->scene_value;

        $expire_seconds = $request->expire_seconds;

        $result = $request->has('forever')
            ? $qrcode->forever($scene_value)
            : $qrcode->temporary($scene_value, $expire_seconds);

        $url = $qrcode->url($result->ticket);

        return $this->response(compact('url'));
    }

    /**
     * 返回指定授权公众号 appid 的 JSSDK 配置
     *
     * @link https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115
     *
     * @param JSSDKRequest|Request $request
     * @param $appid
     * @return array
     * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function authorizerJSSDK(JSSDKRequest $request, $appid)
    {
        if (! $this->inWechatApp($request->userAgent())) {
            return $this->response(['config' => ['debug' => 'not in wx browser']]);
        }

        $app_js = (new AppFactory())->authorizerApp($appid)->jssdk;

        $app_js->setUrl($request->target_url);

        $default_configs = [
            "chooseImage", "uploadImage",
            "chooseWXPay", "openAddress",
            'onMenuShareAppMessage', 'onMenuShareTimeline' ,
            'closeWindow' , 'showAllNonBaseMenuItem' ,
            'scanQRCode' , 'hideAllNonBaseMenuItem',
            'getLocation', 'openLocation'
        ];

        $config_index_arr = array_merge($default_configs, (array) $request->configs);

        $debug = (bool) ($request->debug ?: config('pkx.wechat.jssdk_debug'));

        $config = $app_js->getConfigArray(
            $config_index_arr,
            $debug ,
            true
        );

        return $this->response(compact('config'));
    }

    /**
     * @param $event
     */
    private function log(Collection $event)
    {
        if (config('app.debug')) logger($event->all());
    }

    private function inWechatApp(string $userAgent): bool
    {
        if (! config('app.debug')) return true;
        /**
         * @see HomePageRedirectTest::test_app_skin_detect_login_redirect_to_wx_oauth
         */
        if (str_contains(config('app.env'), 'dusk')) return false;

        return str_contains(mb_strtolower($userAgent), 'micromessenger');
    }
}
